use std::{convert::Infallible, sync::Arc};

use http::{StatusCode, status::InvalidStatusCode};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApiErrorItem {
    pub status: u16,
    pub code: String,
    pub cause: String,
}

pub trait ApiErrors {
    fn api_errors() -> Vec<ApiErrorItem>;
}

impl ApiErrors for Infallible {
    fn api_errors() -> Vec<ApiErrorItem> {
        vec![]
    }
}

impl<T: ApiErrors> ApiErrors for Arc<T> {
    fn api_errors() -> Vec<ApiErrorItem> {
        T::api_errors()
    }
}

impl<T: ApiErrors> ApiErrors for Box<T> {
    fn api_errors() -> Vec<ApiErrorItem> {
        T::api_errors()
    }
}

impl From<StatusCode> for ApiErrorItem {
    fn from(value: StatusCode) -> Self {
        let status = value.as_u16();
        let code = if status < 400 {
            String::from("0")
        } else {
            value.as_str().to_string()
        };
        Self {
            status,
            code,
            cause: value.canonical_reason().unwrap_or_default().into(),
        }
    }
}

impl From<Infallible> for ApiErrorItem {
    fn from(_: Infallible) -> Self {
        ApiErrorItem::success()
    }
}

impl TryFrom<ApiErrorItem> for StatusCode {
    type Error = InvalidStatusCode;

    fn try_from(value: ApiErrorItem) -> Result<Self, Self::Error> {
        StatusCode::from_u16(value.status)
    }
}

impl ApiErrorItem {
    pub fn new(status: u16, code: String, cause: String) -> Self {
        Self {
            status,
            code,
            cause,
        }
    }

    pub fn success() -> Self {
        StatusCode::OK.into()
    }

    pub fn cause_from_status(status: u16) -> String {
        let status_code = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
        let api_error_item = ApiErrorItem::from(status_code);
        api_error_item.cause
    }
}
